import   std.getopt : config, getopt, GetoptResult, GetOptException, defaultGetoptPrinter;
import     std.file : exists, isDir, isFile, FileException, remove;
import    std.stdio : stderr, writeln, File;
import     std.conv : to;
import std.datetime : Clock, SysTime;
import    std.array : split, replace;
import std.typecons : Tuple;
import   std.random : Random, uniform, unpredictableSeed;
import std.algorithm: canFind;

import source.config : ConfigManager;
import source.logger : LoggerWriter;
import source.irc : Irc;
import source.modules : ModuleManager;
import source.oparse : OutputParser;
import source.sharedmemory : SHMmanager;

int main(string[] args)
{
    int tfKey;
    {
        Random gen = Random(unpredictableSeed);
        tfKey = uniform(100000, 999999, gen);
    }
    ConfigManager configuration;
    LoggerWriter logger = new LoggerWriter();
    ModuleManager functManager;
    OutputParser parser;
    {
        string configpath, modulepath;
        {
            GetoptResult helpi;
            try
            {
                helpi = getopt(args,
                    config.required, "config|c", "Path to the config folder", &configpath,
                    config.required, "module|m", "Path to the module folder", &modulepath);
            }
            catch (GetOptException e)
            {
                stderr.writeln(e.msg);
                stderr.writeln("\nConsider running with --help");
                return 1;
            }
            catch (Exception e)
            {
                stderr.writeln(e.msg);
                stderr.writeln("\nConsider running with --help");
                return 1;
            }

            if (helpi.helpWanted)
            {
                defaultGetoptPrinter("A modular I.R.C. bot written in dlang\n", helpi.options);
                return 0;
            }

            try
            {
                if (!isDir(configpath))
                {
                    stderr.writeln(configpath, ": No such directory");
                    return 1;
                }
                else if (!isDir(modulepath))
                {
                    stderr.writeln(modulepath, ": No such directory");
                    return 1;
                }
            }
            catch (FileException e)
            {
                stderr.writeln(e.msg);
                return 1;
            }

            // Check files

            try
            {
                if (!isFile(configpath~"/settings.sdl"))
                {
                    stderr.writeln(configpath~"/settings.sdl", ": No such file");
                    return 1;
                }
                if (!isFile(configpath~"/assets.json"))
                {
                    stderr.writeln(configpath~"/assets.sdl", ": No such file");
                    return 1;
                }
            }
            catch (FileException e)
            {
                stderr.writeln(e.msg);
                return 1;
            }
        }

        string tempLog = "";

        configuration = new ConfigManager(configpath, modulepath);

        if (configuration.reloadConfig())
        {
            writeln("Successfully initialised configuration");
            tempLog ~= "Successfully initialised configuration";
        }
        else
        {
            stderr.writeln("Failed to initalise configuration");
            return 1;
        }

        if (configuration.loggingToggle)
        {
            logger.setPath(configuration.loggingPath);
            logger.nl();
            logger.write(tempLog);
            logger.write("-- Initialised Logger --", "info");
        }

        if (configuration.reloadAssets())
        {
            logger.write("Successfully initialised assets");
        }
        else
        {
            logger.write("Failed to intialise assets", "error");
            return 1;
        }

        functManager = new ModuleManager(modulepath, configpath, configuration, logger, tfKey);
    }


    logger.write("Connecting to server \""~configuration.server~"\" on port "~to!string(configuration.port), "info");

    SHMmanager persist = new SHMmanager("Norbit." ~ to!string(tfKey), 1100, 0);
    scope(exit) remove("Norbit." ~ to!string(tfKey));

    Irc iconn = new Irc(configuration.server, configuration.port, logger);
    iconn.connect();
    scope(exit) iconn.destroy(configuration.quitmsg);
    iconn.register(configuration.nickname, configuration.username, configuration.realname, configuration.pass);
    parser = new OutputParser(configuration, logger, functManager, iconn);
    logger.write("Setup connection", "info");
    functManager.reloadModules();

    bool run = true;
    bool auth = false;
    while (run)
    {
        string raw = iconn.receive();
        string[] ex = split(raw, " ");
        ex.length = 10;

        if (raw != "")
        {
            logger.write(">> "~raw);
        }

        if (ex[0] == "ERROR")
        {
            logger.write("Problem in irc connection", "error");
            return 0;
        }
        else if ((ex[1] == "376" || ex[1] == "422") && !auth)
        {
            switch (configuration.authentication["method"])
            {
                case "none":
                    logger.write("Not authenticating..");
                    break;
                case "auth":
                    iconn.send("PRIVMSG Q@CServe.quakenet.org :AUTH " ~
                        configuration.authentication["username"] ~ " " ~
                        configuration.authentication["password"]);
                    iconn.send("MODE " ~ configuration.nickname ~ " +x");
                    break;
                case "nickserv":
                    iconn.send("PRIVMSG NickServ :identify " ~
                        configuration.authentication["username"] ~ " " ~
                        configuration.authentication["password"]);
                    break;
                default:
                    logger.write("Unknows authentication method found \"" ~
                        configuration.authentication["method"]~"\"");
            }

            for (int i = 0; i < configuration.chans.length; i++)
            {
                iconn.joinChannel(configuration.chans[i], configuration.chankeys[configuration.chans[i]]);
            }

            iconn.setTimeout();

            auth = true;
        }
        else
        {
            if (auth && raw != "" && configuration.ignored.canFind(ex[0]) == false)
            {
                functManager.runModules(raw);
            }
            else
            {
                while (!persist.isEmpty())
                {
                    string toParse = persist.deleteFirst();
                    if (parser.parseOutput(toParse.replace("\n", "")) == 1)
                    {
                        return 0;
                    }
                }
            }
        }
    }

    return 0;
}
